<?php
declare(strict_types = 1);
/**
 * 针对要求高精度的计算工具类，需要php安装bc扩展
 *
 * User: Shanla
 * Date: 2021/1/9
 * Time: 6:00 PM
 */

namespace tool;


class BcTool
{
    /**
     * 比较任意精度的数字
     *
     * @author Shanla
     * @param string $left
     * @param string $right
     * @param int|null $scale
     * @return int 0-相等 1-left大于right -1-right大于left
     */
    public function comp(string $left, string $right, ?int $scale = null) : int
    {
        if (!is_string($left)) {
            $left = (string)$left;
        }
        if (!is_string($right)) {
            $right = (string)$right;
		}
		// php8.0支持scale为null
		$scale = is_null($scale) ? 0 : $scale;
        return bccomp($left, $right, $scale);
    }

	/**
	 * 返回数字数组中最大的数字
	 *
	 * @author Shanla
	 * @param array $numArray
	 * @param int $scale 精确到小数后几位，null为整数
	 * @return string
	 */
	public function compMax(array $numArray, ?int $scale = null) : string
	{
		$max = null;
		foreach($numArray as $num) {
			if ($max == null) {
				$max = $num;
			} else if ($this->comp($num, $max, $scale) > 0) {
				$max = $num;
			}
		}
		return $max;
	}

	/**
	 * 返回数字数组中最小的数字
	 *
	 * @author Shanla
	 * @param array $numArray
	 * @param int $scale 精确到小数后几位，null为整数
	 * @return string
	 */
	public function compMin(array $numArray, ?int $scale = null) : string
	{
		$min = null;
		foreach($numArray as $num) {
			if ($min == null) {
				$min = $num;
			} else if ($this->comp($num, $min, $scale) < 0) {
				$min = $num;
			}
		}
		return $min;
	}

	/**
	 * 两数相加，$scale为null会保留最大的小数
	 *
	 * @author Shanla
	 * @param string $num1
	 * @param string $num2
	 * @param int|null $scale
	 * @return string
	 */
	public function add(string $num1, string $num2, ?int $scale = null) : string
	{
		// 判断传进来的数字是否是数字，如果不是数字返回0
		if (!preg_match('/^\+?(\d+)(\.\d+)?$/', $num1, $tmp1) ||
			!preg_match('/^\+?(\d+)(\.\d+)?$/', $num2, $tmp2)) {
			return '0';
		}

		// grep_match的第三个参数是将匹配到的字符串赋值，$tmp[0]是匹配到的全部字符串，$tmp[1]是第一个条件的字符串，以此类推，这里的$tmp[2]是小数部分
		// 得到小数部分并将小数点和尾部的0去掉
		$dec1 = isset($tmp1[2]) ? rtrim(substr($tmp1[2], 1), '0') : '';
		$dec2 = isset($tmp2[2]) ? rtrim(substr($tmp2[2], 1), '0') : '';

		// 获取到最长的小数长度，用做scale
		$decLen = max(strlen($dec1), strlen($dec2));

		if (null === $scale) {
			$scale = $decLen;
		}

		$sum = bcadd($num1, $num2, $scale);
		$sum = rtrim($sum, '0');
		return $sum;
	}
	
	/**
	 * 两数相减，$scale为null会保留最大的小数
	 *
	 * @author Shanla
	 * @param string $num1
	 * @param string $num2
	 * @param int|null $scale
	 * @return string
	 */
	public function sub(string $num1, string $num2, ?int $scale = null) : string
	{
		// 判断传进来的数字是否是数字，如果不是数字返回0
		if (!preg_match('/^\+?(\d+)(\.\d+)?$/', $num1, $tmp1) ||
			!preg_match('/^\+?(\d+)(\.\d+)?$/', $num2, $tmp2)) {
			return '0';
		}

		// grep_match的第三个参数是将匹配到的字符串赋值，$tmp[0]是匹配到的全部字符串，$tmp[1]是第一个条件的字符串，以此类推，这里的$tmp[2]是小数部分
		// 得到小数部分并将小数点和尾部的0去掉
		$dec1 = isset($tmp1[2]) ? rtrim(substr($tmp1[2], 1), '0') : '';
		$dec2 = isset($tmp2[2]) ? rtrim(substr($tmp2[2], 1), '0') : '';

		// 获取到最长的小数长度，用做scale
		$decLen = max(strlen($dec1), strlen($dec2));

		if (null === $scale) {
			$scale = $decLen;
		}

		$sub = bcsub($num1, $num2, $scale);
		$sub = rtrim($sub, '0');
		return $sub;
	}
	
	/**
	 * 两数相乘，$scale为null会保留最大的小数
	 *
	 * @author Shanla
	 * @param string $num1
	 * @param string $num2
	 * @param int|null $scale
	 * @return string
	 */
	public function mul(string $num1, string $num2, ?int $scale = null) : string
	{
		// 判断传进来的数字是否是数字，如果不是数字返回0
		if (!preg_match('/^\+?(\d+)(\.\d+)?$/', $num1, $tmp1) ||
			!preg_match('/^\+?(\d+)(\.\d+)?$/', $num2, $tmp2)) {
			return '0';
		}

		// grep_match的第三个参数是将匹配到的字符串赋值，$tmp[0]是匹配到的全部字符串，$tmp[1]是第一个条件的字符串，以此类推，这里的$tmp[2]是小数部分
		// 得到小数部分并将小数点和尾部的0去掉
		$dec1 = isset($tmp1[2]) ? rtrim(substr($tmp1[2], 1), '0') : '';
		$dec2 = isset($tmp2[2]) ? rtrim(substr($tmp2[2], 1), '0') : '';

		$decLen1 = strlen($dec1);
		$decLen2 = strlen($dec2);
		if (0 == $decLen1 || 0 == $decLen2) {
			$decLen = $decLen1 == 0 ? $decLen2 : $decLen1;
		} else {
			$decLen = $decLen1 * $decLen2;
		}

		if (null === $scale) {
			$scale = $decLen;
		}

		$mul = bcmul($num1, $num2, $scale);
		$mul = rtrim($mul, '0');
		return $mul;
	}

	/**
	 * 两数相除
	 *
	 * @author Shanla
	 * @param string $mol
	 * @param string $den
	 * @param int $scale
	 * @return string
	 * @throws \Exception
	 */
	public function div(string $mol, string $den, int $scale = 4) : string
	{
		if (0 == $den) {
			throw new \Exception('除数不能为空');
		}

		$div = bcdiv($mol, $den, $scale);
		$div = rtrim($div, '0');
		return $div;
	}
}
